/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

XreaL source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
#include "qbsp.h"


int             c_fogFragment;
int             c_fogPatchFragments;

/*
====================
DrawSurfToMesh
====================
*/
mesh_t         *DrawSurfToMesh(drawSurface_t * ds)
{
	mesh_t         *m;

	m = malloc(sizeof(*m));
	m->width = ds->patchWidth;
	m->height = ds->patchHeight;
	m->verts = malloc(sizeof(m->verts[0]) * m->width * m->height);
	memcpy(m->verts, ds->verts, sizeof(m->verts[0]) * m->width * m->height);

	return m;
}


/*
====================
SplitMeshByPlane
====================
*/
void SplitMeshByPlane(mesh_t * in, vec3_t normal, float dist, mesh_t ** front, mesh_t ** back)
{
	int             w, h, split;
	float           d[MAX_PATCH_SIZE][MAX_PATCH_SIZE];
	drawVert_t     *dv, *v1, *v2;
	int             c_front, c_back, c_on;
	mesh_t         *f, *b;
	int             i;
	float           frac;
	int             frontAprox, backAprox;

	for(i = 0; i < 2; i++)
	{
		dv = in->verts;
		c_front = 0;
		c_back = 0;
		c_on = 0;
		for(h = 0; h < in->height; h++)
		{
			for(w = 0; w < in->width; w++, dv++)
			{
				d[h][w] = DotProduct(dv->xyz, normal) - dist;
				if(d[h][w] > ON_EPSILON)
				{
					c_front++;
				}
				else if(d[h][w] < -ON_EPSILON)
				{
					c_back++;
				}
				else
				{
					c_on++;
				}
			}
		}

		*front = NULL;
		*back = NULL;

		if(!c_front)
		{
			*back = in;
			return;
		}
		if(!c_back)
		{
			*front = in;
			return;
		}

		// find a split point
		split = -1;
		for(w = 0; w < in->width - 1; w++)
		{
			if((d[0][w] < 0) != (d[0][w + 1] < 0))
			{
				if(split == -1)
				{
					split = w;
					break;
				}
			}
		}

		if(split == -1)
		{
			if(i == 1)
			{
				Sys_FPrintf(SYS_VRB, "No crossing points in patch\n");
				*front = in;
				return;
			}

			in = TransposeMesh(in);
			InvertMesh(in);
			continue;
		}

		// make sure the split point stays the same for all other rows
		for(h = 1; h < in->height; h++)
		{
			for(w = 0; w < in->width - 1; w++)
			{
				if((d[h][w] < 0) != (d[h][w + 1] < 0))
				{
					if(w != split)
					{
						Sys_Printf("multiple crossing points for patch -- can't clip\n");
						*front = in;
						return;
					}
				}
			}
			if((d[h][split] < 0) == (d[h][split + 1] < 0))
			{
				Sys_Printf("differing crossing points for patch -- can't clip\n");
				*front = in;
				return;
			}
		}

		break;
	}


	// create two new meshes
	f = malloc(sizeof(*f));
	f->width = split + 2;
	if(!(f->width & 1))
	{
		f->width++;
		frontAprox = 1;
	}
	else
	{
		frontAprox = 0;
	}
	if(f->width > MAX_PATCH_SIZE)
	{
		Error("MAX_PATCH_SIZE after split");
	}
	f->height = in->height;
	f->verts = malloc(sizeof(f->verts[0]) * f->width * f->height);

	b = malloc(sizeof(*b));
	b->width = in->width - split;
	if(!(b->width & 1))
	{
		b->width++;
		backAprox = 1;
	}
	else
	{
		backAprox = 0;
	}
	if(b->width > MAX_PATCH_SIZE)
	{
		Error("MAX_PATCH_SIZE after split");
	}
	b->height = in->height;
	b->verts = malloc(sizeof(b->verts[0]) * b->width * b->height);

	if(d[0][0] > 0)
	{
		*front = f;
		*back = b;
	}
	else
	{
		*front = b;
		*back = f;
	}

	// distribute the points
	for(w = 0; w < in->width; w++)
	{
		for(h = 0; h < in->height; h++)
		{
			if(w <= split)
			{
				f->verts[h * f->width + w] = in->verts[h * in->width + w];
			}
			else
			{
				b->verts[h * b->width + w - split + backAprox] = in->verts[h * in->width + w];
			}
		}
	}

	// clip the crossing line
	for(h = 0; h < in->height; h++)
	{
		dv = &f->verts[h * f->width + split + 1];
		v1 = &in->verts[h * in->width + split];
		v2 = &in->verts[h * in->width + split + 1];
		frac = d[h][split] / (d[h][split] - d[h][split + 1]);
		for(i = 0; i < 10; i++)
		{
			dv->xyz[i] = v1->xyz[i] + frac * (v2->xyz[i] - v1->xyz[i]);
		}
		dv->xyz[10] = 0;		//set all 4 colors to 0 
		if(frontAprox)
		{
			f->verts[h * f->width + split + 2] = *dv;
		}
		b->verts[h * b->width] = *dv;
		if(backAprox)
		{
			b->verts[h * b->width + 1] = *dv;
		}
	}

	/*
	   PrintMesh( in );
	   Sys_Printf("\n");
	   PrintMesh( f );
	   Sys_Printf("\n");
	   PrintMesh( b );
	   Sys_Printf("\n");
	 */

	FreeMesh(in);
}


/*
====================
ChopPatchByBrush
====================
*/
qboolean ChopPatchByBrush(drawSurface_t * ds, bspBrush_t * b)
{
	int             i, j;
	side_t         *s;
	plane_t        *plane;
	mesh_t         *outside[MAX_BRUSH_SIDES];
	int             numOutside;
	mesh_t         *m, *front, *back;
	drawSurface_t  *newds;

	m = DrawSurfToMesh(ds);
	numOutside = 0;

	// only split by the top and bottom planes to avoid
	// some messy patch clipping issues

	for(i = 4; i <= 5; i++)
	{
		s = &b->sides[i];
		plane = &mapPlanes[s->planenum];

		SplitMeshByPlane(m, plane->normal, plane->dist, &front, &back);

		if(!back)
		{
			// nothing actually contained inside
			for(j = 0; j < numOutside; j++)
			{
				FreeMesh(outside[j]);
			}
			return qfalse;
		}
		m = back;

		if(front)
		{
			if(numOutside == MAX_BRUSH_SIDES)
			{
				Error("MAX_BRUSH_SIDES");
			}
			outside[numOutside] = front;
			numOutside++;
		}
	}

	// all of outside fragments become seperate drawsurfs
	c_fogPatchFragments += numOutside;
	for(i = 0; i < numOutside; i++)
	{
		newds = DrawSurfaceForMesh(outside[i]);
		newds->shaderInfo = ds->shaderInfo;
		FreeMesh(outside[i]);
	}

	// replace ds with m
	ds->patchWidth = m->width;
	ds->patchHeight = m->height;
	ds->numVerts = m->width * m->height;
	free(ds->verts);
	ds->verts = malloc(ds->numVerts * sizeof(*ds->verts));
	memcpy(ds->verts, m->verts, ds->numVerts * sizeof(*ds->verts));

	FreeMesh(m);

	return qtrue;
}

//===============================================================================

/*
====================
WindingFromDrawSurf
====================
*/
winding_t      *WindingFromDrawSurf(drawSurface_t * ds)
{
	winding_t      *w;
	int             i;

	w = AllocWinding(ds->numVerts);
	w->numpoints = ds->numVerts;
	for(i = 0; i < ds->numVerts; i++)
	{
		VectorCopy(ds->verts[i].xyz, w->p[i]);
	}
	return w;
}

/*
====================
ChopFaceByBrush

There may be a fragment contained in the brush
====================
*/
qboolean ChopFaceByBrush(drawSurface_t * ds, bspBrush_t * b)
{
	int             i, j;
	side_t         *s;
	plane_t        *plane;
	winding_t      *w;
	winding_t      *front, *back;
	winding_t      *outside[MAX_BRUSH_SIDES];
	int             numOutside;
	drawSurface_t  *newds;
	drawVert_t     *dv;
	shaderInfo_t   *si;
	float           mins[2];

	// brush primitive :
	// axis base
	vec3_t          texX, texY;
	vec_t           x, y;

	w = WindingFromDrawSurf(ds);
	numOutside = 0;

	for(i = 0; i < b->numsides; i++)
	{
		s = &b->sides[i];
		if(s->backSide)
		{
			continue;
		}
		plane = &mapPlanes[s->planenum];

		// handle coplanar outfacing (don't fog)
		if(ds->side->planenum == s->planenum)
		{
			return qfalse;
		}

		// handle coplanar infacing (keep inside)
		if((ds->side->planenum ^ 1) == s->planenum)
		{
			continue;
		}

		// general case
		ClipWindingEpsilon(w, plane->normal, plane->dist, ON_EPSILON, &front, &back);
		FreeWinding(w);
		if(!back)
		{
			// nothing actually contained inside
			for(j = 0; j < numOutside; j++)
			{
				FreeWinding(outside[j]);
			}
			return qfalse;
		}
		if(front)
		{
			if(numOutside == MAX_BRUSH_SIDES)
			{
				Error("MAX_BRUSH_SIDES");
			}
			outside[numOutside] = front;
			numOutside++;
		}
		w = back;
	}

	// all of outside fragments become seperate drawsurfs
	// linked to the same side
	c_fogFragment += numOutside;
	s = ds->side;

	for(i = 0; i < numOutside; i++)
	{
		newds = DrawSurfaceForSide(ds->mapBrush, s, outside[i]);
		FreeWinding(outside[i]);
	}


	// replace ds->verts with the verts for w
	ds->numVerts = w->numpoints;
	free(ds->verts);

	ds->verts = malloc(ds->numVerts * sizeof(*ds->verts));
	memset(ds->verts, 0, ds->numVerts * sizeof(*ds->verts));

	si = s->shaderInfo;

	mins[0] = 9999;
	mins[1] = 9999;

	// compute s/t coordinates from brush primitive texture matrix
	// compute axis base
	ComputeAxisBase(mapPlanes[s->planenum].normal, texX, texY);

	for(j = 0; j < w->numpoints; j++)
	{
		dv = ds->verts + j;
		VectorCopy(w->p[j], dv->xyz);

		if(g_bBrushPrimit == BPRIMIT_OLDBRUSHES)
		{
			// calculate texture s/t
			dv->st[0] = s->vecs[0][3] + DotProduct(s->vecs[0], dv->xyz);
			dv->st[1] = s->vecs[1][3] + DotProduct(s->vecs[1], dv->xyz);
			dv->st[0] /= si->width;
			dv->st[1] /= si->height;
		}
		else
		{
			// calculate texture s/t from brush primitive texture matrix
			x = DotProduct(dv->xyz, texX);
			y = DotProduct(dv->xyz, texY);
			dv->st[0] = s->texMat[0][0] * x + s->texMat[0][1] * y + s->texMat[0][2];
			dv->st[1] = s->texMat[1][0] * x + s->texMat[1][1] * y + s->texMat[1][2];
		}

		if(dv->st[0] < mins[0])
		{
			mins[0] = dv->st[0];
		}
		if(dv->st[1] < mins[1])
		{
			mins[1] = dv->st[1];
		}

		// copy normal
		VectorCopy(mapPlanes[s->planenum].normal, dv->normal);
	}

	// adjust the texture coordinates to be as close to 0 as possible
	if(!si->globalTexture)
	{
		mins[0] = floor(mins[0]);
		mins[1] = floor(mins[1]);
		for(i = 0; i < w->numpoints; i++)
		{
			dv = ds->verts + i;
			dv->st[0] -= mins[0];
			dv->st[1] -= mins[1];
		}
	}

	return qtrue;
}

//===============================================================================


/*
=====================
FogDrawSurfs

Call after the surface list has been pruned, 
before tjunction fixing
before lightmap allocation
=====================
*/
void FogDrawSurfs(void)
{
	int             i, j, k;
	drawSurface_t  *ds;
	bspBrush_t     *b;
	vec3_t          mins, maxs;
	int             c_fogged;
	int             numBaseDrawSurfs;
	dfog_t         *fog;

	Sys_FPrintf(SYS_VRB, "----- FogDrawsurfs -----\n");

	c_fogged = 0;
	c_fogFragment = 0;

	// find all fog brushes
	for(b = entities[0].brushes; b; b = b->next)
	{
		if(!(b->contents & CONTENTS_FOG))
		{
			continue;
		}

		if(numFogs == MAX_MAP_FOGS)
		{
			Error("MAX_MAP_FOGS");
		}
		fog = &dfogs[numFogs];
		numFogs++;
		fog->brushNum = b->outputNumber;

		// find a side with a valid shaderInfo
		// non-axial fog columns may have bevel planes that need to be skipped
		for(i = 0; i < b->numsides; i++)
		{
			if(b->sides[i].shaderInfo && (b->sides[i].shaderInfo->contents & CONTENTS_FOG))
			{
				strcpy(fog->shader, b->sides[i].shaderInfo->shader);
				break;
			}
		}
		if(i == b->numsides)
		{
			continue;			// shouldn't happen
		}

		fog->visibleSide = -1;

		// clip each surface into this, but don't clip any of
		// the resulting fragments to the same brush
		numBaseDrawSurfs = numMapDrawSurfs;
		for(i = 0; i < numBaseDrawSurfs; i++)
		{
			ds = &mapDrawSurfs[i];

			// bound the drawsurf
			ClearBounds(mins, maxs);
			for(j = 0; j < ds->numVerts; j++)
			{
				AddPointToBounds(ds->verts[j].xyz, mins, maxs);
			}

			// check against the fog brush
			for(k = 0; k < 3; k++)
			{
				if(mins[k] > b->maxs[k])
				{
					break;
				}
				if(maxs[k] < b->mins[k])
				{
					break;
				}
			}
			if(k < 3)
			{
				continue;		// bboxes don't intersect
			}

			if(ds->mapBrush == b)
			{
				int             s;

				s = ds->side - b->sides;
				if(s <= 6)
				{				// not one of the reversed inside faces
					// this is a visible fog plane
					if(fog->visibleSide != -1)
					{
						Sys_Printf("WARNING: fog brush %i has multiple visible sides\n", b->brushnum);
					}
					fog->visibleSide = s;
				}
			}

			if(ds->miscModel)
			{
				// we could write splitting code for trimodels if we wanted to...
				c_fogged++;
				ds->fogNum = numFogs - 1;
			}
			else if(ds->patch)
			{
				if(ChopPatchByBrush(ds, b))
				{
					c_fogged++;
					ds->fogNum = numFogs - 1;
				}
			}
			else
			{
				if(ChopFaceByBrush(ds, b))
				{
					c_fogged++;
					ds->fogNum = numFogs - 1;
				}
			}
		}
	}

	// split the drawsurfs by the fog brushes

	Sys_FPrintf(SYS_VRB, "%5i fogs\n", numFogs);
	Sys_FPrintf(SYS_VRB, "%5i fog polygon fragments\n", c_fogFragment);
	Sys_FPrintf(SYS_VRB, "%5i fog patch fragments\n", c_fogPatchFragments);
	Sys_FPrintf(SYS_VRB, "%5i fogged drawsurfs\n", c_fogged);
}
